package org.zn.note.java8;

import org.zn.note.java8.Bean.Dish;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.*;
import java.util.stream.*;

import static java.util.stream.Collectors.toList;

/**
 * {@link java.util.stream.Stream} 流
 * 定义：从“支持数据处理的源”生成的“元素序列”
 * <p>
 * 1、元素序列：和集合一样，Stream提供接口可以访问特定元素类型的一组有序值。
 * 集合是数据结构，强调以特定时间/空间复杂度，在一组元素中，增删改查元素。
 * 集合偏向数据，流偏向计算。
 * 2、(数据)源
 * 流会使用一个提供数据的源（集合、数组、IO资源等）。有序集合生成流会保留原有顺序；列表生成流，元素顺序和列表一致。
 * <p>
 * 集合像DVD，包含所有的值；流像在线视频，边看边输出，按需计算；
 * 比如你要一个包含所有质数的集合，是不可能的，因为质数有无穷多个，这个集合创建不出来，就没法输出给你；
 * 而创建一个这样的流是可以的，你随时需要，它就可以给你值。
 * <p>
 * 流不是数据结构，所以流不存储数据!!!
 * 流只是在数据源的基础定义一组操作。
 * <p>
 * 流的操作有两种：Intermediate(中间操作)、Terminal(终止操作)。
 * 中间操作，就是按定义(调用)的顺序，来组成这个操作集合，而并有实际执行。中间操作返回的仍然是Stream。
 * 终止操作，真正执行，真正的在数据源上遍历筛选，来执行上面的操作集合。一次循环。
 * <p>
 * 流只能被消费一次（循环一次），从数据源获取一个流被消费后，就需要重新获取流。
 * 什么是被消费（循环一次）呢？ 就是执行了终止操作。
 * <p>
 * 其实java 8 新增的这个 Stream，有点类似数据库的sql，都是在数据集上进行操作，最终选出需要的数据。
 * Stream的终止操作，就是将这些需要的数据以什么样的形式输出。
 * <p>
 * 用Stream，让用户更多的关心选取数据的业务逻辑，而不是关心如果选取数据(循环、遍历)的过程。
 * 代码写出来，更像是在描述业务场景。
 * <p>
 * 从技术上讲，Stream库的内部迭代可以自动选择一种适合当前硬件的数据表示和并行实现。
 * <p>
 * 集合可以直接.stream，数组不能直接.stream，要用{@link Arrays#stream(Object[])}
 * <p>
 * 过滤filter：
 * {@link Stream#filter(Predicate)}、{@link Predicate#test(Object)}
 * 排序sorted：有状态-有界
 * {@link Stream#sorted()}、{@link Stream#sorted(Comparator)}、{@link Comparator#compare(Object, Object)}
 * 跳过skip：有状态-有界
 * {@link Stream#skip(long)}
 * 截断limit：有状态-有界
 * {@link Stream#limit(long)}
 * 不重复distinct：有状态-有界
 * {@link Stream#distinct()}  根据元素hashCode、equals
 * <p>
 * 映射map：Stream<T> ---Function--> Stream<R>
 * {@link Stream#map(Function)}，传入{@link Function#apply(Object)}
 * 这个就是把流中的要素，传入给这个函数，函数经过计算后(自己写的lombok逻辑)，返回一个数据，可以还是这个类型的，也可以是其他类型的
 * 这里是会把原来流的数据类型改变的
 * 比如一个单词列表List<Word>，我想要每个单词的长度List<Integer>，就可以.map(Word::getLength)
 * 比如一个菜品列表List<Dish>，我想要每个菜品的名字List<String>，就可以.map(Dish::getName)
 * map可以接map
 * 比如我想要每个菜品名字的长度List<int>，就可以.map(Dish::getName).map(String::length)
 * <p>
 * 扁平化映射flatMap： Stream<Strean<T>> -----> Stream<T>  {@link #testFlatMap}
 * {@link Stream#flatMap(Function)}，传入{@link Function#apply(Object)}
 * 虽然传入的也是Function<T, R>，但对Function返回的R做了限制，要求R必须是一个Stream
 * 相当于flatMap会收到N个Stream<X>，它会把这些Stream合并成(扁平化)一个Stream
 * 返回 Stream<X> = Stream { stream1中x1+x2+x3+...   +   stream2中x1+x2+x3...   +   ...  +   streamN中x1+x2+x3...}
 * 如果是map，这里会返回Stream<Stream<X>>
 * 如果是flatMap，就是Stream<X>了，把里面的要素合并了
 * <p>
 * Stream 查找和匹配
 * 首先，这些都是终止操作，因为返回的不再是一个Stream。
 * 其次，这些都是断路操作，当有确定的结果后，不需要处理完整个流，就能返回，同样的还有 {@link Stream#limit(long)}
 * {@link Stream#anyMatch(Predicate)}
 * {@link Stream#allMatch(Predicate)}
 * {@link Stream#noneMatch(Predicate)}
 * {@link Stream#findAny()}
 * {@link Stream#findFirst()}
 * <p>
 * Stream 归约
 * 终止操作。将最终的数据通过指定的规则，合成一个值。
 * 将一组数据，通过计算，归成一个值，叫归约操作reduce，在函数式编程中，也叫折叠fold。
 * {@link Stream#reduce(Object, BinaryOperator)} 有状态，有界  {@link #testReduce()}
 * <p>
 * Stream 原始类型流
 * {@link IntStream}、{@link LongStream}、{@link DoubleStream}
 * 流中的数据指定为int、long、double，避免了装箱拆箱。也新增了一些归约方法，比如max、sum、average等。
 * 普通流转化为数值流：
 * {@link Stream#mapToInt(ToIntFunction)}
 * {@link Stream#mapToLong(ToLongFunction)}
 * {@link Stream#mapToDouble(ToDoubleFunction)}
 * 数值流转化为普通流：
 * {@link IntStream#boxed()}
 */
public class Ch04_Ch05_Stream {

    public static void main(String[] args) {
        List<String> result = Dish.genDishs().stream() // Stream<Dish>
                // 过滤，入参：Predicate<T>，唯一抽象方法：boolean test(T t);
                .filter(dish -> (dish.getCalories() < 700)) // 卡路里小于700
                // 按卡路里排序
                .sorted(Comparator.comparingInt(Dish::getCalories))
                // 跳过头N个元素
                .skip(2)
                // 限制数量、截断
                .limit(4)
                // 不重复（hashCode、equals）
                .distinct()
                // 执行操作，入参：Function<T, R>，唯一抽象方法：R apply(T t);
                // 这里其实会传入一个T类型的实例，然后经过操作(自己写lombok)，返回任意一种类型的实例
                // 一般用途就是在一个bean中，抽取一个属性
                // 在执行这个前，每个元素都是Dish，执行后每个元素就都是String了
                .map(Dish::getName) // 只要菜名
                // import static java.util.stream.Collectors.toList;
                .collect(toList());
        System.out.println(result);
    }

    /**
     * 流的扁平化 {@link Stream#flatMap(Function)}
     */
    private static void testFlatMap() {
        // 给定单词列表 ["Hello","World"]，要求返回不同的字符 ["H","e","l", "o","W","r","d"]
        // 数组不能直接.stream，要用Arrays.stream
        Stream<String> stringStream = Arrays.stream(new String[]{"Hello", "World"});
        // 方案一：不行。逻辑也不对，返回类型也不对
        stringStream
                .map(word -> word.split("")) // 返回 Stream<String[]>
                .distinct() // 这里是针对每一个String[]，做distinct，逻辑不对
                .collect(toList()); // 返回 List<String[]>，返回类型不对
        // 方案二：不行。逻辑也不对，返回类型也不对
        stringStream
                .map(word -> word.split("")) // 返回 Stream<String[]>
                .map(Arrays::stream) // 返回 Stream<Stream<String>>
                .distinct() // 针对每一个Stream<String>对象，做distinct，逻辑不对
                .collect(toList()); // 返回 List<Stream<String>>，返回类型不对
        // 方案三：
        stringStream
                .map(w -> w.split("")) // 把每一个String，变成String[]，返回依然是Stream<String[]>
                // flatMap 接收的也是Function，但是对Function有限制，要求这个Function必须返回一个Stream！
                // Arrays::stream，会把每个String[]，转成Stream<String>
                // flatMap会接收到N个Stream<String>，它会把这些Stream合并(扁平化)
                // 也就是拿出所有的Stream<String>，中的String，然后组成一个Stream<String>
                // 相当于Stream<String>[N] --> Stream{ stream1中s1+s2+s3... + stream2中s1+s2+s3 + ... }
                .flatMap(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
    }

    /**
     * Stream 匹配和查找
     */
    private static void testMatchAndFind() {
        List<Dish> menu = Dish.genDishs();
        // 看菜单里是否有素菜
        if (menu.stream().anyMatch(Dish::isVegetarian))
            System.out.println("The menu is (somewhat) vegetarian friendly!!");
        // 看菜单里得卡路里是否全都小于1000
        if (menu.stream().allMatch(d -> d.getCalories() < 1000))
            System.out.println("All dishs are healthy");
        // 同上
        if (menu.stream().noneMatch(d -> d.getCalories() >= 1000))
            System.out.println("All dishs are healthy");
        // 拿出任意一个素菜
        Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();
        dish.ifPresent(d -> System.out.println(d));
        // 在串行的时候，和findAny一样。在并行时，有限制
        Optional<Dish> dish2 = menu.stream().filter(Dish::isVegetarian).findFirst();
        dish2.ifPresent(d -> System.out.println(d));
    }

    /**
     * Stream 归约
     * {@link Stream#reduce(BinaryOperator)}
     * {@link Stream#reduce(Object, BinaryOperator)} 带初始值的Object
     * {@link Stream#reduce(Object, BiFunction, BinaryOperator)}
     * <p>
     * {@link BinaryOperator}
     * {@link BiFunction#apply(Object, Object)}
     * interface BinaryOperator<T> extends BiFunction<T,T,T>
     * interface BiFunction<T, U, R>
     * R apply(T t, U u);
     * 给两个值，经过计算，输出一个值
     */
    private static void testReduce() {
        List<Integer> numbers = Arrays.asList(9, 5, 1, 3, 4, 2, 6, 7, 8);
        // 初始值0，把这些数累加起来
        numbers.stream().reduce(0, (a, b) -> a + b);
        numbers.stream().reduce(0, Integer::sum);
        // 最大值
        numbers.stream().reduce((a, b) -> a.compareTo(b) > 0 ? a : b);
        numbers.stream().reduce((a, b) -> Integer.max(a, b));
        numbers.stream().reduce(Integer::max);
    }

    /**
     * 整形数值流
     */
    private static void testIntStream() {
        // 实例1：计算所有菜品的卡路里
        Dish.genDishs().stream()
                .map(Dish::getCalories)
                .reduce(0, Integer::sum); // 这里就有一个装箱的过程，int calories，被转换成了Integer
        Dish.genDishs().stream()
                .mapToInt(Dish::getCalories) // IntStream
                .sum(); // 这种方式就不会装箱
        // 实例2：生成[1, 100]的偶数流
        // IntStream.rangeClosed(1, 100) 表示 [1, 100]
        // IntStream.range(1, 100) 表示 [1, 100)
        IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
    }
}
